package com.billy.ohos.swipe.consumer;

import com.billy.ohos.swipe.SmartSwipe;
import com.billy.ohos.swipe.SmartSwipeWrapper;
import com.billy.ohos.swipe.SwipeConsumer;
import com.billy.ohos.swipe.internal.ScrimView;
import com.billy.ohos.swipe.internal.SwipeHelper;
import com.billy.ohos.swipe.listener.SwipeListener;
import ohos.agp.components.*;
import ohos.agp.utils.Rect;

import java.util.HashMap;

import static com.billy.ohos.swipe.SmartSwipeWrapper.INVISIBLE;
import static com.billy.ohos.swipe.internal.SwipeUtil.getReverseDirection;
import static ohos.agp.components.Component.*;

/**
 * contains content view and at most 4 drawer view
 * drawer view shows above content view
 * default release mode is {@link #RELEASE_MODE_AUTO_OPEN_CLOSE}
 *
 * @author billy.qi
 */
public class DrawerConsumer extends SwipeConsumer implements Component.ClickedListener {
    protected final Component[] mDrawerViews = new Component[4];
    protected Component mCurDrawerView;
    protected int leftA, topA, rightA, bottomA;
    protected int mScrimColor = 0;
    protected int mShadowColor = 0;
    protected ScrimView mScrimView;
    protected int mShadowSize;
    protected boolean mDrawerViewRequired = true;
    protected boolean mShowScrimAndShadowOutsideContentView;
    private static HashMap<Integer, Boolean> ordered = new HashMap<>();
    private ComponentContainer.LayoutConfig params;
    private int mLeft;
    private int mTop;
    private int mRight;
    private int mBottom;

    public DrawerConsumer() {
        setReleaseMode(SwipeConsumer.RELEASE_MODE_AUTO_OPEN_CLOSE);
    }

    @Override
    public void onAttachToWrapper(SmartSwipeWrapper wrapper, SwipeHelper swipeHelper) {
        ordered.clear();
        super.onAttachToWrapper(wrapper, swipeHelper);
        for (int i = 0; i < mDrawerViews.length; i++) {
            attachDrawerView(i);
        }
        if (mShadowSize == 0) {
            mShadowSize = SmartSwipe.dp2px(10, wrapper.getContext());
        }
    }

    @Override
    protected void initChildrenFormXml() {
        final SmartSwipeWrapper wrapper = mWrapper;
        int childCount = wrapper.getChildCount();
        Component contentView = wrapper.getContentView();
        for (int i = 0; i < childCount; i++) {
            Component child = wrapper.getComponentAt(i);
            if (child == contentView || !(child.getLayoutConfig() instanceof SmartSwipeWrapper.LayoutParams)) {
                continue;
            }
            final int gravity = ((SmartSwipeWrapper.LayoutParams) child.getLayoutConfig()).gravity;
            if (mDrawerViews[0] == null && (gravity & DIRECTION_LEFT) == DIRECTION_LEFT) {
                // This child is a left drawer
                setLeftDrawerView(child);
                mWrapper.consumeInflateFromXml();
            }
            if (mDrawerViews[1] == null && (gravity & DIRECTION_RIGHT) == DIRECTION_RIGHT) {
                setRightDrawerView(child);
                mWrapper.consumeInflateFromXml();
            }
            if (mDrawerViews[2] == null && (gravity & DIRECTION_TOP) == DIRECTION_TOP) {
                setTopDrawerView(child);
                mWrapper.consumeInflateFromXml();
            }
            if (mDrawerViews[3] == null && (gravity & DIRECTION_BOTTOM) == DIRECTION_BOTTOM) {
                // This child is a bottom drawer
                setBottomDrawerView(child);
                mWrapper.consumeInflateFromXml();
            }
        }
    }

    @Override
    public void onDetachFromWrapper() {
        super.onDetachFromWrapper();
        if (mScrimView != null) {
            mWrapper.removeComponent(mScrimView);
            mScrimView.setClickedListener(null);
            mScrimView = null;
        }
        for (Component drawerView : mDrawerViews) {
            if (drawerView != null) {
                mWrapper.removeComponent(drawerView);
            }
        }
        mCurDrawerView = null;
    }

    @Override
    protected void onOpened() {
        super.onOpened();
        if (mScrimView != null && !mShowScrimAndShadowOutsideContentView) {
            mScrimView.setClickedListener(this);
        }
    }

    @Override
    protected void onClosed() {
        super.onClosed();
        if (mCurDrawerView != null) {
            changeDrawerViewVisibility(INVISIBLE);
        }
        if (mScrimView != null) {
            mScrimView.setClickedListener(null);
            mScrimView.setClickable(false);
            mScrimView.setFocusable(FOCUS_DISABLE);
            mScrimView.setVisibility(HIDE);
        }
    }

    @Override
    public boolean tryAcceptMoving(int pointerId, float downX, float downY, float dx, float dy) {
        boolean handle = super.tryAcceptMoving(pointerId, downX, downY, dx, dy);
        if (handle && mCachedSwipeDistanceX == 0 && mCachedSwipeDistanceY == 0) {
            if (mDrawerViewRequired && getDrawerView(mDirection) == null) {
                handle = false;
            }
        }
        return handle;
    }

    @Override
    public void onSwipeAccepted(int activePointerId, boolean settling, float initialMotionX, float initialMotionY) {
        if (mCachedSwipeDistanceX == 0 && mCachedSwipeDistanceY == 0) {
            changeDrawerViewVisibility(INVISIBLE);
            mCurDrawerView = getDrawerView(mDirection);
            changeDrawerViewVisibility(VISIBLE);
        }
        int width = mWidth;
        int height = mHeight;
        if (mCurDrawerView != null) {
            width = mCurDrawerView.getEstimatedWidth();
            height = mCurDrawerView.getEstimatedHeight();
        } else if (mDrawerViewRequired) {
            return;
        }
        if (!mOpenDistanceSpecified) {
            if ((mDirection & DIRECTION_HORIZONTAL) > 0) {
                mOpenDistance = width;
            } else {
                mOpenDistance = height;
            }
        }
        calculateDrawerDirectionInitPosition(mDirection, width, height);
        changeDrawerViewVisibility(VISIBLE);
        initScrimView();
        layoutChildren();
        orderChildren();
        super.onSwipeAccepted(activePointerId, settling, initialMotionX, initialMotionY);
    }

    protected void changeDrawerViewVisibility(int visibility) {
        if (mCurDrawerView != null) {
            mCurDrawerView.setVisibility(visibility);
        }
    }

    @Override
    public void setCurrentStateAsClosed() {
        mCurDrawerView = null;
        super.setCurrentStateAsClosed();
    }

    protected void initScrimView() {
        if (mScrimColor != 0 || mShadowColor != 0 && mShadowSize > 0) {
            if (mScrimView == null) {
                mScrimView = new ScrimView(mWrapper.getContext());
                mWrapper.addComponent(mScrimView);
            }

            mScrimView.setClickable(false);
            mScrimView.setScrimColor(mScrimColor);
            if (mShadowColor != 0 && mShadowSize > 0) {
                int shadowDirection = this.mDirection;
                if (mShowScrimAndShadowOutsideContentView) {
                    shadowDirection = getReverseDirection(mDirection);
                }
                mScrimView.setDirection(this.mDirection, mShadowColor, shadowDirection, mShadowSize, mWidth, mHeight);
            }
            mScrimView.setVisibility(VISIBLE);
        }

    }

    protected void calculateDrawerDirectionInitPosition(int direction, int width, int height) {
        switch (direction) {
            case DIRECTION_LEFT:
                leftA = -width;
                rightA = leftA + width;
                topA = 0;
                bottomA = height;
                break;
            case DIRECTION_RIGHT:
                leftA = mWidth;
                rightA = leftA + width;
                topA = 0;
                bottomA = height;
                break;
            case DIRECTION_TOP:
                leftA = 0;
                rightA = mWidth;
                topA = -height;
                bottomA = topA + height;
                break;
            case DIRECTION_BOTTOM:
                leftA = 0;
                rightA = mWidth;
                topA = mHeight;
                bottomA = topA + height;
                break;
            default:
                break;
        }
    }

    @Override
    public boolean onLayout(boolean changed, int left, int top, int right, int bottom) {
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        if (mWrapper != null) {
            layoutChildren();
            return true;
        }
        return false;
    }

    @Override
    protected void onDisplayDistanceChanged(int distanceXToDisplay, int distanceYToDisplay, int dx, int dy) {
        Component drawerView = mCurDrawerView;
        if (drawerView != null && drawerView.getComponentParent() == mWrapper) {
            boolean horizontal = (mDirection & DIRECTION_HORIZONTAL) > 0;
            Rect rect = drawerView.getComponentPosition();
            if (horizontal) {
                rect.left += dx;
                rect.right += dx;
                drawerView.setComponentPosition(rect);
            } else {
                rect.top += dy;
                rect.bottom += dy;
                drawerView.setComponentPosition(rect);
            }
            layoutScrimView();
        }
    }

    protected void orderChildren() {
        if (mCurDrawerView != null) {
            Boolean result = ordered.get(mCurDrawerView.hashCode());
            if (result != null && result) {
                return;
            }
            ordered.put(mCurDrawerView.hashCode(), true);
            ComponentParent componentParent = mCurDrawerView.getComponentParent();
            componentParent.moveChildToFront(mCurDrawerView);
        }
        if (mScrimView != null) {
            ComponentParent componentParent = mScrimView.getComponentParent();
            componentParent.moveChildToFront(mScrimView);
        }
    }

    protected void layoutChildren() {
        layoutContentView(mWrapper.getContentView());
        layoutDrawerView();
        layoutScrimView();
    }

    protected void layoutContentView(Component contentView) {
        if (contentView != null) {
            contentView.arrange(0, 0, mWidth, mHeight);

        }
    }

    protected void layoutDrawerView() {
        if (mCurDrawerView != null && mCurDrawerView.getVisibility() == VISIBLE) {
            mCurDrawerView.arrange(leftA + mCurDisplayDistanceX, topA + mCurDisplayDistanceY, rightA - leftA, bottomA - topA);
        }
    }

    protected void layoutScrimView() {
        if (mScrimView != null && mScrimView.getVisibility() == VISIBLE) {
            int lP = 0, rP = mWidth, tP = 0, bP = mHeight;
            if (mShowScrimAndShadowOutsideContentView) {
                switch (mDirection) {
                    case DIRECTION_LEFT:
                        rP = mCurDisplayDistanceX;
                        break;
                    case DIRECTION_RIGHT:
                        lP = rP + mCurDisplayDistanceX;
                        break;
                    case DIRECTION_TOP:
                        bP = mCurDisplayDistanceY;
                        break;
                    case DIRECTION_BOTTOM:
                        tP = bP + mCurDisplayDistanceY;
                        break;
                    default:
                }
            } else {
                switch (mDirection) {
                    case DIRECTION_LEFT:
                        lP = mCurDisplayDistanceX;
                        break;
                    case DIRECTION_RIGHT:
                        rP = rP + mCurDisplayDistanceX;
                        break;
                    case DIRECTION_TOP:
                        tP = mCurDisplayDistanceY;
                        break;
                    case DIRECTION_BOTTOM:
                        bP = bP + mCurDisplayDistanceY;
                        break;
                    default:
                }
            }
            mScrimView.arrange(lP, tP, rP - lP, bP - tP);
            mScrimView.setProgress(mShowScrimAndShadowOutsideContentView ? (1 - mProgress) : mProgress);
        }
    }

    @Override
    protected void notifySwipeStart() {
        if (mCurDrawerView instanceof SwipeListener) {
            ((SwipeListener) mCurDrawerView).onSwipeStart(mWrapper, this, mDirection);
        }
        super.notifySwipeStart();
    }

    @Override
    protected void notifySwipeProgress(boolean settling) {
        if (mCurDrawerView instanceof SwipeListener) {
            ((SwipeListener) mCurDrawerView).onSwipeProcess(mWrapper, this, mDirection, settling, mProgress);
        }
        super.notifySwipeProgress(settling);
    }

    @Override
    protected void notifySwipeRelease(float xVelocity, float yVelocity) {
        if (mCurDrawerView instanceof SwipeListener) {
            ((SwipeListener) mCurDrawerView).onSwipeRelease(mWrapper, this, mDirection, mProgress, xVelocity, yVelocity);
        }
        super.notifySwipeRelease(xVelocity, yVelocity);
    }

    public Component getDrawerView(int direction) {
        int viewIndex = -1;
        switch (direction) {
            default:
                break;
            case DIRECTION_LEFT:
                viewIndex = 0;
                break;
            case DIRECTION_RIGHT:
                viewIndex = 1;
                break;
            case DIRECTION_TOP:
                viewIndex = 2;
                break;
            case DIRECTION_BOTTOM:
                viewIndex = 3;
                break;
        }
        if (viewIndex < 0) {
            return null;
        }
        return mDrawerViews[viewIndex];
    }

    public DrawerConsumer setLeftDrawerView(Component drawerView) {
        return setDrawerView(DIRECTION_LEFT, drawerView);
    }

    public DrawerConsumer setRightDrawerView(Component drawerView) {
        return setDrawerView(DIRECTION_RIGHT, drawerView);
    }

    public DrawerConsumer setTopDrawerView(Component drawerView) {
        return setDrawerView(DIRECTION_TOP, drawerView);
    }

    public DrawerConsumer setBottomDrawerView(Component drawerView) {
        return setDrawerView(DIRECTION_BOTTOM, drawerView);
    }

    public DrawerConsumer setHorizontalDrawerView(Component drawerView) {
        return setDrawerView(DIRECTION_HORIZONTAL, drawerView);
    }

    public DrawerConsumer setVerticalDrawerView(Component drawerView) {
        return setDrawerView(DIRECTION_VERTICAL, drawerView);
    }

    public DrawerConsumer setAllDirectionDrawerView(Component drawerView) {
        return setDrawerView(DIRECTION_ALL, drawerView);
    }

    /**
     * set a extension to the direction, also set direction enable if drawerView is not null(otherwise, disable the direction)
     * direction can be a single direction or mixed direction(eg: DIRECTION_LEFT | DIRECTION_RIGHT)
     *
     * @param direction  direction to set
     * @param drawerView view
     * @return this
     */
    public DrawerConsumer setDrawerView(int direction, Component drawerView) {
        Component newComponent;
        if (drawerView != null && drawerView instanceof Text) {
            ComponentContainer directionalLayout;
            if (drawerView.getComponentParent() != null) {
                drawerView.getComponentParent().removeComponent(drawerView);
            }
            directionalLayout = new DirectionalLayout(drawerView.getContext());
            directionalLayout.setLayoutConfig(new StackLayout.LayoutConfig(StackLayout.LayoutConfig.MATCH_CONTENT, StackLayout.LayoutConfig.MATCH_PARENT));
            directionalLayout.addComponent(drawerView);
            newComponent = directionalLayout;
        } else {
            newComponent = drawerView;
        }
        enableDirection(direction, newComponent != null);
        if ((direction & DIRECTION_LEFT) > 0) {
            setOrUpdateDrawerView(0, newComponent);
        }
        if ((direction & DIRECTION_RIGHT) > 0) {
            setOrUpdateDrawerView(1, newComponent);
        }
        if ((direction & DIRECTION_TOP) > 0) {
            setOrUpdateDrawerView(2, newComponent);
        }
        if ((direction & DIRECTION_BOTTOM) > 0) {
            setOrUpdateDrawerView(3, newComponent);
        }
        return this;
    }

    private void setOrUpdateDrawerView(int index, Component drawerView) {
        Component oldView = mDrawerViews[index];
        if (oldView == drawerView) {
            return;
        }
        mDrawerViews[index] = drawerView;
        attachDrawerView(index);
    }

    private void attachDrawerView(final int index) {
        final Component drawerView = mDrawerViews[index];
        final SmartSwipeWrapper wrapper = mWrapper;
        if (drawerView != null && wrapper != null && drawerView.getComponentParent() != wrapper) {
            if (drawerView.getComponentParent() != null) {
                ((ComponentContainer) drawerView.getComponentParent()).removeComponent(drawerView);
            }
            int contentViewIndex = wrapper.getChildIndex(wrapper.getContentView());
            if (contentViewIndex >= 0) {
                ComponentContainer.LayoutConfig lp = drawerView.getLayoutConfig();
                if (lp == null) {
                    int wp = StackLayout.LayoutConfig.MATCH_CONTENT, hp = StackLayout.LayoutConfig.MATCH_CONTENT;
                    switch (index) {
                        default:
                            break;
                        case 0:
                        case 1:
                            hp = StackLayout.LayoutConfig.MATCH_PARENT;
                            break;
                        case 2:
                        case 3:
                            wp = StackLayout.LayoutConfig.MATCH_PARENT;
                            break;
                    }
                    lp = new StackLayout.LayoutConfig(wp, hp);
                    drawerView.setLayoutConfig(lp);
                }
                wrapper.addComponent(drawerView, contentViewIndex);
                drawerView.setVisibility(INVISIBLE);
            }
        }
    }

    private boolean checkLayoutParams(ComponentContainer.LayoutConfig params) {
        return params != null;
    }

    @Override
    public int getOpenDistance() {
        if (mCurDrawerView == null) {
            return super.getOpenDistance();
        }
        if ((mDirection & DIRECTION_HORIZONTAL) > 0) {
            return mCurDrawerView.getEstimatedWidth();
        }
        return mCurDrawerView.getEstimatedHeight();
    }

    /**
     * Set a color to use for the scrim that obscures primary content while a drawer is open.
     *
     * @param color Color to use in 0xAARRGGBB format.
     * @return this
     */
    public DrawerConsumer setScrimColor(int color) {
        mScrimColor = color;
        return this;
    }

    /**
     * Set a color to use for the shadow at the edge of content view while a drawer is open.
     *
     * @param shadowColor Color to use in 0xAARRGGBB format.
     * @return this
     */
    public DrawerConsumer setShadowColor(int shadowColor) {
        mShadowColor = shadowColor;
        return this;
    }

    public int getShadowSize() {
        return mShadowSize;
    }

    /**
     * set the size of shadow at the edge of content view while a drawer is open.
     *
     * @param size shadow size in pixel
     * @return this
     */
    public DrawerConsumer setShadowSize(int size) {
        this.mShadowSize = size;
        return this;
    }

    public boolean isDrawerViewRequired() {
        return mDrawerViewRequired;
    }

    /**
     * set the extension view as drawer is required or not
     * it useful inside this sdk framework,
     * developers who use this SDK do not call this function unless you really know what its mean
     *
     * @param required required or not
     * @return this
     */
    public DrawerConsumer setDrawerViewRequired(boolean required) {
        this.mDrawerViewRequired = required;
        return this;
    }

    public boolean isScrimAndShadowOutsideContentView() {
        return mShowScrimAndShadowOutsideContentView;
    }

    public DrawerConsumer showScrimAndShadowOutsideContentView() {
        this.mShowScrimAndShadowOutsideContentView = true;
        return this;
    }

    public DrawerConsumer showScrimAndShadowInsideContentView() {
        this.mShowScrimAndShadowOutsideContentView = false;
        return this;
    }

    @Override
    public void onClick(Component component) {
        if (getDragState() == SwipeHelper.STATE_IDLE && !mShowScrimAndShadowOutsideContentView && component == mScrimView) {
            smoothClose();
        }
    }

    @Override
    protected boolean canChildScroll(ComponentContainer parentView, int direction, int pointerId, float downX, float downY, float dx, float dy) {
        if (mDirection != DIRECTION_NONE && mWrapper.getContentView() == findTopChildUnder(parentView, (int) downX, (int) downY)) {
            return false;
        }
        return super.canChildScroll(parentView, direction, pointerId, downX, downY, dx, dy);
    }
}